import { canUse, isUndefined, toString, BackendServices } from "mmb";
import { RedisClient, createClient, print } from "redis";
import { promisify } from "util";
import { getRedisAddress, getRedisPassword } from "../store/helper";

export interface RedisContext {
  cl: RedisClient | undefined;
  available: boolean;
  retryInterval: number;
  retryMax: number;
  totalRetryTime: number;
  srvPrefix: string;
  getFn: ((key: string) => Promise<string | null>) | undefined;
  incrFn: ((key: string) => Promise<number | null>) | undefined;
  mgetFn: ((...args: string[]) => Promise<string[]>) | undefined;
  mgetN: ((...args: string[]) => Promise<number[]>) | undefined;
  setFn:
    | ((key: string, ttl: number, val: string) => Promise<string>)
    | undefined;
  setN:
    | ((key: string, ttl: number, val: number) => Promise<number>)
    | undefined;
  expireFn: ((key: string, ttl: number) => Promise<number>) | undefined;
  cacheTTL: number;
}
const ctx: RedisContext = {
  cl: undefined,
  available: false,
  retryInterval: 5000,
  retryMax: 12 * 30,
  totalRetryTime: 1000 * 60 * 30,
  srvPrefix: BackendServices.BGAPI, // blinggroup 缩写
  getFn: undefined,
  incrFn: undefined,
  mgetFn: undefined,
  mgetN: undefined,
  setFn: undefined,
  setN: undefined,
  expireFn: undefined,
  cacheTTL: 60 * 60 * 2, // 120分钟
};

export const makeEntityKey = (entity: string) => (pk: string): string => {
  return `${ctx.srvPrefix}:${entity}:${pk}`;
};

export const makeUserKey = (userId: string) => (pk: string): string => {
  return `${ctx.srvPrefix}:${userId}:${pk}`;
};

export const makeRouteRLUserKey = (
  method: string,
  url: string,
  userId: string
) => (pk: string): string => {
  return `${ctx.srvPrefix}:RL:${method}${url}:${userId}:${pk}`;
};

export const setCache = (key: string, val: any, ttl = ctx.cacheTTL) => {
  if (!ctx.available || isUndefined(ctx.setFn)) {
    console.log(`redis setFn is not available`);
    return;
  }
  return ctx.setFn(key, ttl, toString(val));
};

export const getCache = (key: string) => {
  if (!ctx.available || isUndefined(ctx.getFn)) {
    console.log(`redis getFn is not available`);
    return;
  }
  return ctx.getFn(key);
};

export const mgetCache = (...args: string[]) => {
  if (!ctx.available || isUndefined(ctx.mgetFn)) {
    console.log(`redis mgetFn is not available`);
    return Promise.resolve([]);
  }
  return ctx.mgetFn(...args);
};

export const expireCache = (key: string, ttl: number = 0) => {
  if (!ctx.available || isUndefined(ctx.expireFn)) {
    console.log(`redis expireFn is not available`);
    return;
  }
  return ctx.expireFn(key, ttl);
};

export const incrCache = (key: string) => {
  if (!ctx.available || isUndefined(ctx.incrFn)) {
    console.log(`redis incrFn is not available`);
    return;
  }
  return ctx.incrFn(key);
};

export const init = () => {
  if (isUndefined(ctx.cl)) {
    let address = getRedisAddress();
    let password = getRedisPassword();
    if (!canUse(address) || !canUse(password)) {
      console.log("redis cofig err=>", address, password);
      return;
    }
    ctx.cl = createClient(6379, address!, {
      password: password!,
      retry_strategy: function (options) {
        if (options.error && options.error.code === "ECONNREFUSED") {
          // End reconnecting on a specific error and flush all commands with
          // a individual error
          return new Error("The server refused the connection");
        }
        if (options.total_retry_time > ctx.totalRetryTime) {
          // End reconnecting after a specific timeout and flush all commands
          // with a individual error
          return new Error("Retry time exhausted");
        }
        if (options.attempt > ctx.retryMax) {
          // End reconnecting with built in error
          return undefined;
        }
        // reconnect after
        return Math.min(options.attempt * 100, ctx.retryInterval);
      },
    });
  }
  ctx.getFn = promisify(ctx.cl.get).bind(ctx.cl);
  ctx.incrFn = promisify<string, number>(ctx.cl.incr).bind(ctx.cl);
  ctx.setFn = promisify(ctx.cl.setex).bind(ctx.cl);
  ctx.mgetFn = promisify(ctx.cl.mget).bind(ctx.cl);
  ctx.expireFn = promisify(ctx.cl.expire).bind(ctx.cl);
  // const v = await setex_async("bgapi", 5, "redis-client")
  const srv = BackendServices.BGAPI;
  ctx.cl.setex(srv, 10, "bgapi ok", print);
  ctx.cl.get(srv, (err, reply) => {
    if (err) {
      console.log("redis get err:", err);
      return;
    }
    console.log("redis test=>", reply);
    ctx.available = true;

    console.log("ctx.mgetFn=>", ctx.mgetFn);
    ctx.mgetFn!(srv).then((res) => {
      console.log("mget res =>", res);
    });
  });
  ctx.cl.keys("*", function (err, keys) {
    if (err) return console.log(err);

    for (let key of keys) {
      if (key.indexOf(`${ctx.srvPrefix}:RL:`) != -1) {
        ctx.expireFn!(key, 0)
      }
      console.log(key);
    }
  });
};
